<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace crcl\base\structure;

interface ICollection
{
  /**
   * @param $sUri
   *
   * @return \crcl\base\structure\ICollection $oCollection
   */
  public static function createFromConfig($sUri);
  public static function createFromCommon($sIni);
  public function setConfig($sUri);

  public function getDsn();
  public function getDsnReplica();
  public function getTable();

  public function commit();
  public function rollback();
  public function isDirty();

  public function fromArray($aData);
  public function toArray();
  public function getFields();

  public function setUniqueId($sKey);

  public function persistance();
}

class Collection implements ICollection, \Iterator, \ArrayAccess
{
  public static function createFromConfig($sUri)
  {
    $oCollection = new static;
    $oCollection->setConfig($sUri);

    return $oCollection;
  }

  public static function createFromCommon($sIni)
  {
    $sIni = 'ini://' . \crcl\base\application\Directories::items() . $sIni . '.ini';
    $oCollection = self::createFromConfig($sIni);
    return $oCollection;
  }

  public function setConfig($sUri)
  {
    $this->sUri = $sUri;
  }

  public function getDsn()
  {
    $sDsn = \crcl\base\Config::reader($this->sUri)
      ->get(\crcl\base\structure\Item::CONFIG_META, Item::CONFIG_META_DSN);

    return $sDsn;
  }

  public function getDsnReplica()
  {
    $sDsn = \crcl\base\Config::reader($this->sUri)
        ->get(\crcl\base\structure\Item::CONFIG_META, Item::CONFIG_META_DSN_REPLICA);
    if (empty($sDsn))
    {
      throw new Exception('No replica');
    }
    return $sDsn;
  }


  public function getName()
  {
    $sName = \crcl\base\Config::reader($this->sUri)
      ->get(Item::CONFIG_META, Item::CONFIG_META_NAME);
    return $sName;
  }

  public function getCollectionName()
  {
    $sName = \crcl\base\Config::reader($this->sUri)->setDefault(\NULL)
      ->get(Item::CONFIG_META, Item::CONFIG_META_NAME_COLLECTION);
    if (\is_null($sName))
    {
      $sName = $this->getName() . 's';
    }
    return $sName;
  }

  public function getTable()
  {
    $sTable = \crcl\base\resource\Reader::getInstance($this->getDsn())->getTable();
    return $sTable;
  }

  public function getFields()
  {
    $aFields = \crcl\base\Config::reader($this->sUri)
      ->get(Item::CONFIG_META, Item::CONFIG_META_FIELDS);
    return $aFields;
  }

  public function getTableFields()
  {
    $sTable = $this->getTable();
    $aFields = \crcl\base\Config::reader($this->sUri)
      ->get(Item::CONFIG_META, Item::CONFIG_META_FIELDS);
    foreach ($aFields as $key => $sField)
    {
      $aFields[$key] = $sTable . '.' . $sField;
    }
    return $aFields;
  }

  public function __get($sRow)
  {
    if (\array_key_exists($sRow, $this->aRows))
    {
      return $this->aRows[$sRow];
    }

    throw new Exception('Unknown row.',
      \crcl\base\structure\Exception::ERROR_COLLECTION_UNKNOWNROW,
      array('row' => $sRow));
  }

  public function commit()
  {
    foreach ($this->aRows as $oRow)
    {
      $oRow->commit();
    }
  }

  public function rollback()
  {
    foreach ($this->aRows as $oRow)
    {
      $oRow->rollback();
    }
  }

  public function isDirty()
  {
    foreach ($this->aRows as $oRow)
    {
      if ($oRow->isDirty())
      {
        return \TRUE;
      }
    }
    return \FALSE;
  }

  public function toArray()
  {
    $aReturn = array();
    foreach ($this->aRows as $sKey => $oRow)
    {
      $aReturn[$sKey] = $oRow->toArray();
    }
    return $aReturn;
  }

  //Iterator
  public function rewind()
  {
    return reset($this->aRows);
  }

  public function current()
  {
    return current($this->aRows);
  }

  public function key()
  {
    return key($this->aRows);
  }

  public function next()
  {
    return next($this->aRows);
  }

  public function valid()
  {
    return key($this->aRows) !== \NULL;
  }

  //ArrrayAccess
  public function offsetSet($iOffset, $value)
  {
    if (is_null($iOffset))
    {
      $this->aRows[] = $value;
    }
    else
    {
      $this->aRows[$iOffset] = $value;
    }
  }

  public function offsetExists($iOffset)
  {
    return isset($this->aRows[$iOffset]);
  }

  public function offsetUnset($iOffset)
  {
    unset($this->aRows[$iOffset]);
  }

  public function offsetGet($iOffset)
  {
    return isset($this->aRows[$iOffset]) ? $this->aRows[$iOffset] : \NULL;
  }

  //

  public function fromArray($aData)
  {
    $this->aRows = [];
    foreach ($aData as $key => $aRow)
    {
      $oRow = Item::createFromConfig($this->sUri);
      foreach ($aRow as $field => $data)
      {
        $oRow->$field->set($data);
      }
      $this->aRows[$key] = $oRow;
    }
    return $this;
  }

  public function setUniqueId($sKey)
  {
    $aKeys = array();
    $aRows = array();
    foreach ($this->aRows as $oRow)
    {
      $id = $oRow->$sKey->get();
      if (\in_array($id, $aKeys))
      {
        throw new Exception('Key values not unique.',
          Exception::ERROR_COLLECTION_NOTUNIQUE,
          array('key' => $id));
      }
      $aKeys[] = $id;
      $aRows[$id] = $oRow;
    }
    $this->aRows = $aRows;
  }

  public function persistance()
  {
    return \crcl\base\persistance\Collection::create($this);
  }

  public function addJoinData($sTableJoin, $sFieldLeft, $sFieldRight, $sWhereField, $sWhereValue, $sOperator = '=')
  {
    $this->aJoin['sTableJoin'] = $sTableJoin;
    $this->aJoin['sFieldLeft'] = $sFieldLeft;
    $this->aJoin['sFieldRight'] = $sFieldRight;
    $this->aJoin['sWhereField'] = $sWhereField;
    $this->aJoin['sWhereValue'] = $sWhereValue;
    $this->aJoin['sOperator'] = $sOperator;
  }

  public function getJoinData()
  {
    return $this->aJoin;
  }

  /** PRIVATE *************************************************************************************/

  private $sUri;
  private $aRows = array();
  private $aJoin = array();
}